<?php
declare (strict_types=1);

namespace app\mall\logic\product;

use app\common\exception\LogicException;
use app\common\helper\util\SnowFlake;
use app\common\logic\Transaction;
use app\common\model\payment\PaymentOrder as PO;
use app\common\model\payment\PaymentOrderItem as POI;
use app\common\model\ProductType;
use app\mall\logic\address\Address;
use app\mall\model\product\Product as ProductMod;
use app\user\logic\user\Credit;
use app\user\logic\user\ExpenditureFlow;
use Exception;
use think\annotation\Inject;
use think\db\exception\DbException;
use think\facade\Request;
use think\Model;
use think\Paginator;

/**
 * 后台商品逻辑层
 */
class Product extends ProductMod
{

    /**
     * @Inject()
     * @var Credit
     */
    protected $credit;

    /**
     * 商品列表
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/11/18
     *
     * @param array $map
     * @return Paginator
     * @throws DbException
     */
    public function getList(array $map = []): Paginator
    {
        $field = 'id,title,cover,scribing_price,cash_price,credit_price,sale_count';
        return $this->with(['cover_url'])->field($field)->where($map)->order(['best_sort' => 'asc', 'update_time' => 'desc'])->paginate(static::getListRows());
    }

    /**
     * 商品列表
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/11/18
     *
     * @param array $map
     * @return Paginator
     * @throws DbException
     */
    public function getRecommend(array $map = []): Paginator
    {
        $map[] = ['is_recommend', '=', 1];
        $map[] = ['mhm_id', '=', request()->mhm_id];
        $field = 'id,title,cover,scribing_price,cash_price,credit_price,sale_count';
        return $this->with(['cover_url'])->field($field)->where($map)->order(['best_sort' => 'asc', 'update_time' => 'desc'])->paginate(static::getListRows());
    }

    /**
     * 商品详情页
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/11/18
     *
     * @param $id
     * @return Product|array|Model
     */
    public function getDetail($id)
    {
        $field             = 'id,title,cover,info,scribing_price,cash_price,credit_price,stock_num';
        $data              = $this->with(['swiper', 'monthSaleCount'])->field($field)->findOrEmpty($id);
        $data->month_count = intval($data->month_count);
        $data->info_url    = Request::domain() . Request::baseUrl() . '/info';

        return $data;
    }

    /**
     * 商品介绍
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/12/06
     *
     * @param $id
     * @return mixed
     */
    public function getInfo($id)
    {
        $data = $this->where('id', $id)->field('info')->findOrEmpty();
        return $data->info;
    }

    /**
     * fetchOrder
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/11/23
     *
     * @param $productId
     * @return array
     */
    public function fetchOrder($productId): array
    {
        // 地址
        /** @var Address $addrLogic */
        $addrLogic = app(Address::class);
        $addr      = $addrLogic->getUserOrderAddr();
        // 商品
        $prod = $this->field('title,cover,cash_price,credit_price,fictitious,carriage')->findOrEmpty($productId)->append(['cover_url']);
        if ($prod->isEmpty()) throw new LogicException('商品信息错误');
        // 积分
        /** @var Credit $creditLogic */
        $creditLogic = app(Credit::class);
        $cred        = $creditLogic->getUserProdCreditCash();
        // $cred        = $creditLogic->getUserProdCreditCash($prod->cash_price, $prod->credit_price);

        return ['address' => $addr, 'product' => $prod, 'credit' => $cred];
    }

    public function submitOrder($param)
    {
        // 验证数据
        $this->getValidate()->scene('submit_order')->failException()->check($param);
        // 参数
        $id   = $param['id'];
        $num  = $param['num'];
        $crdt = $param['credit'];
        $addr = $param['addr_id'] ?? 0;
        // 检查库存
        $sn = cache('stock_num_' . $id);
        if ($sn !== null && $sn < $num) throw new LogicException('库存不足');
        // 商品信息
        $data = $this->field('id,mhm_id,cash_price,credit_price,status,stock_num,fictitious,carriage')->lock(true)->findOrEmpty($id);
        if ($data->isEmpty()) throw new LogicException('数据错误', '未查询到数据');
        // 检查库存
        if ($data->stock_num < $num) throw new LogicException('库存不足');
        // 缓存库存
        cache('stock_num_' . $id, $data->stock_num - $num, 5);
        // 检查实体商品地址
        if (!$data->fictitious && !$addr) throw new LogicException('请填写地址');
        $address        = app(Address::class)->field('consignee,phone,areatext,address')->where('id', $addr)->findOrEmpty();
        $data->addrinfo = $address;
        //
        $this->startTrans();
        try {
            // 检查重复订单
            // $this->checkOrderExist($id);
            // 检查积分抵扣
            $data = $this->calcPaymentAndCredit($data, $num, $crdt);
            // 生成订单
            $order = $this->createOrder($data);
            // 抵扣积分操作
            if ($crdt) $this->credit->redeemCredit(intval($crdt), $order->order_no, 6);
            // 减少库存
            $data->stock_num -= $num;
            if (!$data->save()) throw new LogicException('提交失败');
            $this->commit();
            return $order;
        } catch (LogicException|Exception $e) {
            $this->rollback();
            cache('stock_num_' . $id, $data->stock_num, 5);
            throw new LogicException($e->getMessage());
        }
    }

    public function checkOrderExist($id)
    {
        $mid = request()->mid;
        // 检测重复订单
        $map = [
            ['user_id', '=', $mid],
            ['product_id', '=', $id],
            ['product_type_id', '=', 11]
        ];

        $order = app(POI::class)->where($map)->order('order_no', 'DESC')->with(['order_status' => function ($query) {
            $query->whereIn('status', '10,20,30,40');
        }])->findOrEmpty();
        // 已消费
        // halt($order->order_sub_no);
        $paid = app(ExpenditureFlow::class)->where('order_sub_no', $order->order_sub_no)->whereUserId($mid)->whereRelId($id)->findOrEmpty();
        if (!$paid->isEmpty()) throw new LogicException('已购买，请等待后台处理');
        // 存在
        if (!$order->isEmpty() && $order->status && $order->status != 20) throw new LogicException('已存在订单');
    }

    protected function createOrder($data)
    {
        // 主订单数据
        $main = [
            'order_no'    => app(SnowFlake::class)->createPD(),
            'user_id'     => request()->mid,
            'from'        => strtolower(request()->device_type),
            'mhm_id'      => request()->user->mhm_id,
            'payment'     => $data->payment,
            'addrinfo'    => $data->addrinfo,
            'status'      => $data->payment ? 10 : 20,
            'close_time'  => $this->close_time ?: null,
            'create_time' => time(),
            'update_time' => time(),
        ];
        $main['status'] == 20 && $main['payment_time'] = time();
        // 生成主订单
        $order = PO::create($main);
        if ($order->isEmpty()) throw new LogicException('订单生成失败', '主订单生成失败');
        // 子订单数据
        $sub = [
            'order_no'        => $order->order_no,
            'order_sub_no'    => app(SnowFlake::class)->createPI(),
            'mhm_id'          => $data->mhm_id,
            'from'            => strtolower(request()->device_type),
            'user_id'         => request()->mid,
            'product_id'      => $data->id,
            'product_type_id' => 11,
            'payment'         => $data->payment,
            'credit'          => $data->credit,
            'num'             => $data->num,
            'ext_data'        => json_encode(['num' => $data->num, 'cash_price' => $data->cash_price, 'credit_price' => $data->credit_price])];
        // 生成子订单
        $orderItem = POI::create($sub);
        if ($orderItem->isEmpty()) throw new LogicException('订单生成失败', '子订单生成失败');
        // 订单需要支付
        $order->need_pay = $order->status == 10 ? 1 : 0;
        // 支付金额为0时
        if (20 == $order->status) {
            // 创建支付记录
            app(Transaction::class)->addTransaction($order, 'lcnpay', true);
            // 增加销量
            /** @var ProductSale $sale */
            $sale = app(ProductSale::class);
            $sale->addProductSale($data->id, request()->mid, $sub['order_sub_no']);
        }

        return $order;
    }

    private function calcPaymentAndCredit(Product $data, $num, $crdt): Product
    {
        $userCredit = $this->credit->userCredit();
        if ($crdt > $userCredit) throw new LogicException('积分不足');
        // 积分充足时购买，支付金额及积分
        $payment = bcmul(strval($data->cash_price), strval($num), 2);
        $credit  = bcmul(strval($data->credit_price), strval($num));
        // 积分使用超限
        if ($credit < $crdt) throw new LogicException('积分使用超限');
        // 商品所需积分超出用户所有积分
        $over = $credit > $userCredit;
        if ($crdt && $over && $userCredit != $crdt) throw new LogicException('积分变更，请刷新后重试');
        // 使用积分情况下，积分不足时购买，支付金额及积分
        if ($over || !$crdt) {
            $ratio = $this->credit->getRatio();
            if (!$ratio) throw new LogicException('无法使用积分，请联系商家');
            $gap     = bcdiv(bcsub($credit, strval($crdt)), $ratio, 2);
            $payment = bcadd($gap, $payment, 2);
            $credit  = $crdt;
        }
        // 实体商品运费
        $carriage      = $data->fictitious ? '0' : $data->carriage;
        $data->num     = $num;
        $data->payment = floatval(bcadd($payment, $carriage, 2));
        $data->credit  = ['credit' => $credit];

        return $data;
    }

    public function addSaleCount($id, $num)
    {
        $data = $this->findOrEmpty($id);
        if ($data->isEmpty()) throw new LogicException('数据错误');
        $data->sale_count += $num;
        if (!$data->save()) throw new LogicException('销量更新失败');
    }

    public function getTitleById($id)
    {
        return $this->where('id', $id)->value('title');
    }

    /**
     * 订单产品详情
     * Author: 亓官雨树 <lucky.max@foxmail.com>
     * Date: 22/11/30
     *
     * @param $orderItem
     * @return Product|array|Model
     */
    public function getProductInfo($orderItem)
    {
        $info          = $this->with(['cover_url'])->field('id,title,mhm_id,cover,cash_price,credit_price,carriage,fictitious')->findOrEmpty($orderItem->product_id);
        $info->type_id = ProductType::getProductIdByKey('goods');
        $info->payment = $orderItem->payment;
        $info->price   = $info->cash_price;

        return $info;
    }
}
